'''
Created on Jul 27, 2013

@author: Cihat Basol
'''

import json
import subprocess
import time

from dexen.common import constants
from dexen.common import events


def TaskFromJSON(json):
    task_name = json.get("task_name", None)
    cmd_args = json.get("cmd_args", None)
    assert task_name is not None
    assert cmd_args is not None
    event_json = json.get("event", None)
    if event_json:
        event = events.EventFromJSON(event_json)
        return EventTask(task_name, cmd_args, event)
    # Else it is a dataflow task
    condition = json.get("condition", None)
    input_size = json.get("input_size", None)
    assert condition is not None
    assert input_size is not None
    return DataFlowTask(task_name, cmd_args, condition, input_size)


def CreateDataFlowTaskWithData(self, dataflow_task, input_data):
    result = DataFlowTask(dataflow_task.name, dataflow_task.cmd_args,
                          dataflow_task.condition, dataflow_task.input_size)
    result.set_input_data(input_data)
    return result


class Task(object):
    EVENT_TASK = "EVENT_TASK"
    DATAFLOW_TASK = "DATAFLOW_TASK"
    def __init__(self, name, cmd_args):
        self.name = name
        self.cmd_args = cmd_args

    @property
    def is_event_task(self):
        return self.type == Task.EVENT_TASK

    @property
    def is_dataflow_task(self):
        return self.type == Task.DATAFLOW_TASK

    def execute(self, env):
        self.stdout = ""
        self.stderr = ""
        self.begin_time = time.time()

        # Temporary fix for unicode problem on windows
        # http://stackoverflow.com/questions/12253014/why-does-popen-fail-on-windows-if-the-env-parameter-contains-a-unicode-object
        # http://stackoverflow.com/questions/13101653/python-convert-complex-dictionary-of-strings-from-unicode-to-ascii
        def convert(input):
            if isinstance(input, dict):
                return {convert(key): convert(value) for key, value in input.iteritems()}
            elif isinstance(input, list):
                return [convert(element) for element in input]
            elif isinstance(input, unicode):
                return input.encode('utf-8')
            else:
                return input
        env = convert(env)
        # End of fix

        try:
            p = subprocess.Popen(self.cmd_args, stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE, env=env)
        except Exception, ex:
            self.stderr = "Popen Failed"
            print ex
        else:
            while p.poll() is None:
                time.sleep(0.5)
            self.stdout = p.stdout.read()
            self.stderr = p.stderr.read()
        self.end_time = time.time()


class EventTask(Task):
    def __init__(self, name, cmd_args, event):
        super(EventTask, self).__init__(name, cmd_args)
        self.type = Task.EVENT_TASK
        self.event = event

    def execute(self, env):
        super(EventTask, self).execute(env)

    def json(self):
        return {
            "task_name" : self.name,
            "cmd_args" : self.cmd_args,
            "event" : self.event.json()
        }


class DataFlowTask(Task):
    def __init__(self, name, cmd_args, condition, input_size):
        super(DataFlowTask, self).__init__(name, cmd_args)
        self.condition = condition
        self.type = Task.DATAFLOW_TASK
        self.input_size = input_size
        self.input_data = [] # list of DataObjectId

    def set_input_data(self, input_data):
        self.input_data = input_data

    def clone_with_data(self, input_data):
        cloned_task = DataFlowTask(self.name, self.cmd_args, self.condition,
                                   self.input_size)
        cloned_task.set_input_data(input_data)
        return cloned_task

    def execute(self, env):
        input_data = [data.to_json_dict() for data in self.input_data]
        env[constants.ENV_TASK_INPUT_JSON] = json.dumps(input_data)
        super(DataFlowTask, self).execute(env)

    def json(self):
        return {
            "task_name" : self.name,
            "cmd_args" : self.cmd_args,
            "condition" : self.condition,
            "input_size" : self.input_size
        }


class Execution(object):
    """Execution object.
    
    Created in Server Core.
    """
    def __init__(self, execution_id, user_name, job_name, worker_name, task,
                 task_data=None):
        self.execution_id = execution_id
        self.user_name = user_name
        self.job_name = job_name
        self.worker_name = worker_name
        self.task = task
        self.task_name = self.task.name
        self.task_data = task_data
        if task_data:
            self.task_data = [data.to_json_dict() for data in self.task_data]
        self.creation_time = time.time()

    def execute(self, env):
        """Execute the assigned task.
        
        Invoked in Worker but the object is originally created in the 
        ServerCore.
        """
        env[constants.ENV_USER_NAME] = self.user_name
        env[constants.ENV_JOB_NAME] = self.job_name
        env[constants.ENV_EXECUTION_ID] = str(self.execution_id)
        self.task.execute(env)
        return ExecutionResult(self.execution_id, self.user_name, self.job_name,
                               self.task)


class ExecutionResult(object):
    def __init__(self, execution_id, user_name, job_name, task):
        self.execution_id = execution_id
        self.user_name = user_name
        self.job_name = job_name
        self.task = task
        self.task_name = task.name
        self.begin_time = task.begin_time
        self.end_time = task.end_time
        self.stdout = task.stdout
        self.stderr = task.stderr
    
    @property
    def execution_time(self):
        return self.end_time - self.begin_time
